/*
 * Description: 
 *     History: yang@haipo.me, 2017/03/16, create
 */

# include "me_config.h"
# include "me_market.h"
# include "me_balance.h"
# include "me_history.h"
# include "me_message.h"
# include "me_trade.h"

uint64_t order_id_start;
uint64_t deals_id_start;

struct dict_id_key : public dict_key_base{
    uint32_t id = 0;
    dict_id_key(uint32_t _id) : id(_id){}

    virtual uint32_t hash() const override{
        return this->id;
    }
    virtual dict_key_base* dup() override{
        dict_id_key *obj = (dict_id_key*)malloc(sizeof(struct dict_id_key));
        obj->id = this->id;
        return obj;
    }
    virtual int compare(const dict_key_base *key2) const override{
        dict_id_key * k2 = (dict_id_key*)(key2);
        return this->id == k2->id ? 0 : 1;
    }
    virtual void release() override{
        free(this);
    }
};

int market_t::order_put( order_t *order)
{
    if (order->type != MARKET_ORDER_TYPE_LIMIT)
        return -__LINE__;

    dict_id_key order_key( order->id );
    if (this->orders->add(&order_key, order) == NULL)
        return -__LINE__;

    dict_id_key user_key(order->user_id);
    auto entry = this->users->find(&user_key);
    if (entry) {
        if (skiplist_insert(entry->get_value(), order) == NULL)
            return -__LINE__;
    } else {
        skiplist_type type;
        memset(&type, 0, sizeof(type));
        type.compare = order_id_compare;
        skiplist_t *order_list = skiplist_create(&type);
        if (order_list == NULL)
            return -__LINE__;
        if (skiplist_insert(order_list, order) == NULL)
            return -__LINE__;
        if (this->users->add(&user_key, order_list) == NULL)
            return -__LINE__;
    }

    // sell
    if (order->side == MARKET_ORDER_SIDE_ASK) {
        if (skiplist_insert(this->asks, order) == NULL)
            return -__LINE__;
        mpd_copy(order->freeze, order->left, &mpd_ctx);
        if (balance_freeze(order->user_id, this->stock, order->left) == NULL)
            return -__LINE__;
    } else { // buy
        if (skiplist_insert(this->bids, order) == NULL)
            return -__LINE__;
        mpd_t *result = mpd_new(&mpd_ctx);
        mpd_mul(result, order->price, order->left, &mpd_ctx);
        mpd_copy(order->freeze, result, &mpd_ctx);
        if (balance_freeze(order->user_id, this->money, result) == NULL) {
            mpd_del(result);
            return -__LINE__;
        }
        mpd_del(result);
    }
    return 0;
}

int market_t::order_finish(bool real, order_t *order)
{
    // sell
    if (order->side == MARKET_ORDER_SIDE_ASK) {
        skiplist_node *node = skiplist_find(this->asks, order);
        if (node) {
            skiplist_delete(this->asks, node);
        }
        if (mpd_cmp(order->freeze, mpd_zero, &mpd_ctx) > 0) {
            if (balance_unfreeze(order->user_id, this->stock, order->freeze) == NULL) {
                return -__LINE__;
            }
        }
    } else { // buy
        skiplist_node *node = skiplist_find(this->bids, order);
        if (node) {
            skiplist_delete(this->bids, node);
        }
        if (mpd_cmp(order->freeze, mpd_zero, &mpd_ctx) > 0) {
            if (balance_unfreeze(order->user_id, this->money, order->freeze) == NULL) {
                return -__LINE__;
            }
        }
    }

    dict_id_key order_key(order->id);
    this->orders->remove(&order_key);

    dict_id_key user_key(order->user_id);

    auto entry = this->users->find(&user_key);
    if (entry) {
        skiplist_t *order_list = entry->get_value();
        skiplist_node *node = skiplist_find(order_list, order);
        if (node) {
            skiplist_delete(order_list, node);
        }
    }

    if (real) {
        if (mpd_cmp(order->deal_stock, mpd_zero, &mpd_ctx) > 0) {
            int ret = append_order_history(order);
            if (ret < 0) {
                log_fatal("append_order_history fail: %d, order: %" PRIu64 "", ret, order->id);
            }
        }
    }
    order->release();
    return 0;
}

market_t * market_t::create(market *conf)
{
    if (!asset_exist(conf->stock) || !asset_exist(conf->money))
        return NULL;
    if (conf->stock_prec + conf->money_prec > asset_prec(conf->money))
        return NULL;
    if (conf->stock_prec + conf->fee_prec > asset_prec(conf->stock))
        return NULL;
    if (conf->money_prec + conf->fee_prec > asset_prec(conf->money))
        return NULL;

    market_t *m = (market_t*)malloc(sizeof(market_t));
    //memset(m, 0, sizeof(market_t));
    m->name             = strdup(conf->name);
    m->stock            = strdup(conf->stock);
    m->money            = strdup(conf->money);
    m->stock_prec       = conf->stock_prec;
    m->money_prec       = conf->money_prec;
    m->fee_prec         = conf->fee_prec;
    m->min_amount       = mpd_qncopy(conf->min_amount);

    m->users = dict_plus<dict_id_key*,skiplist_t*>::create(1024);
    if (m->users == NULL)
        return NULL;

    m->orders = dict_plus<dict_id_key*,order_t*>::create(1024, false);
    if (m->orders == NULL)
        return NULL;

    skiplist_type lt;
    memset(&lt, 0, sizeof(lt));
    lt.compare = order_match_compare;

    m->asks = skiplist_create(&lt);
    m->bids = skiplist_create(&lt);
    if (m->asks == NULL || m->bids == NULL)
        return NULL;

    return m;
}

int market_t::execute_limit_ask_order(bool real, order_t *taker)
{
    mpd_t *price    = mpd_new(&mpd_ctx);
    mpd_t *amount   = mpd_new(&mpd_ctx);
    mpd_t *deal     = mpd_new(&mpd_ctx);
    mpd_t *ask_fee  = mpd_new(&mpd_ctx);
    mpd_t *bid_fee  = mpd_new(&mpd_ctx);

    skiplist_node *node;
    skiplist_iter *iter = skiplist_get_iterator(this->bids);
    while ((node = skiplist_next(iter)) != NULL) {
        if (mpd_cmp(taker->left, mpd_zero, &mpd_ctx) == 0) {
            break;
        }

        order_t *maker = (order_t*)node->value;
        if (mpd_cmp(taker->price, maker->price, &mpd_ctx) > 0) {
            break;
        }

        mpd_copy(price, maker->price, &mpd_ctx);
        if (mpd_cmp(taker->left, maker->left, &mpd_ctx) < 0) {
            mpd_copy(amount, taker->left, &mpd_ctx);
        } else {
            mpd_copy(amount, maker->left, &mpd_ctx);
        }

        mpd_mul(deal, price, amount, &mpd_ctx);
        mpd_mul(ask_fee, deal, taker->taker_fee, &mpd_ctx);
        mpd_mul(bid_fee, amount, maker->maker_fee, &mpd_ctx);

        taker->update_time = maker->update_time = current_timestamp();
        uint64_t deal_id = ++deals_id_start;
        if (real) {
            append_order_deal_history(taker->update_time, deal_id, taker, MARKET_ROLE_TAKER, maker, MARKET_ROLE_MAKER, price, amount, deal, ask_fee, bid_fee);
            push_deal_message(taker->update_time, this->name, taker, maker, price, amount, ask_fee, bid_fee, MARKET_ORDER_SIDE_ASK, deal_id, this->stock, this->money);
        }

        mpd_sub(taker->left, taker->left, amount, &mpd_ctx);
        mpd_add(taker->deal_stock, taker->deal_stock, amount, &mpd_ctx);
        mpd_add(taker->deal_money, taker->deal_money, deal, &mpd_ctx);
        mpd_add(taker->deal_fee, taker->deal_fee, ask_fee, &mpd_ctx);

        balance_sub(taker->user_id, BALANCE_TYPE_AVAILABLE, this->stock, amount);
        if (real) {
            append_balance_trade_sub(taker, this->stock, amount, price, amount);
        }
        balance_add(taker->user_id, BALANCE_TYPE_AVAILABLE, this->money, deal);
        if (real) {
            append_balance_trade_add(taker, this->money, deal, price, amount);
        }
        if (mpd_cmp(ask_fee, mpd_zero, &mpd_ctx) > 0) {
            balance_sub(taker->user_id, BALANCE_TYPE_AVAILABLE, this->money, ask_fee);
            if (real) {
                append_balance_trade_fee(taker, this->money, ask_fee, price, amount, taker->taker_fee);
            }
        }

        mpd_sub(maker->left, maker->left, amount, &mpd_ctx);
        mpd_sub(maker->freeze, maker->freeze, deal, &mpd_ctx);
        mpd_add(maker->deal_stock, maker->deal_stock, amount, &mpd_ctx);
        mpd_add(maker->deal_money, maker->deal_money, deal, &mpd_ctx);
        mpd_add(maker->deal_fee, maker->deal_fee, bid_fee, &mpd_ctx);

        balance_sub(maker->user_id, BALANCE_TYPE_FREEZE, this->money, deal);
        if (real) {
            append_balance_trade_sub(maker, this->money, deal, price, amount);
        }
        balance_add(maker->user_id, BALANCE_TYPE_AVAILABLE, this->stock, amount);
        if (real) {
            append_balance_trade_add(maker, this->stock, amount, price, amount);
        }
        if (mpd_cmp(bid_fee, mpd_zero, &mpd_ctx) > 0) {
            balance_sub(maker->user_id, BALANCE_TYPE_AVAILABLE, this->stock, bid_fee);
            if (real) {
                append_balance_trade_fee(maker, this->stock, bid_fee, price, amount, maker->maker_fee);
            }
        }

        if (mpd_cmp(maker->left, mpd_zero, &mpd_ctx) == 0) {
            if (real) {
                push_order_message(ORDER_EVENT_FINISH, maker, this);
            }
            this->order_finish(real, maker);
        } else {
            if (real) {
                push_order_message(ORDER_EVENT_UPDATE, maker, this);
            }
        }
    }
    skiplist_release_iterator(iter);

    mpd_del(amount);
    mpd_del(price);
    mpd_del(deal);
    mpd_del(ask_fee);
    mpd_del(bid_fee);

    return 0;
}

int market_t::execute_limit_bid_order(bool real, order_t *taker)
{
    mpd_t *price    = mpd_new(&mpd_ctx);
    mpd_t *amount   = mpd_new(&mpd_ctx);
    mpd_t *deal     = mpd_new(&mpd_ctx);
    mpd_t *ask_fee  = mpd_new(&mpd_ctx);
    mpd_t *bid_fee  = mpd_new(&mpd_ctx);

    skiplist_node *node;
    skiplist_iter *iter = skiplist_get_iterator(this->asks);
    while ((node = skiplist_next(iter)) != NULL) {
        if (mpd_cmp(taker->left, mpd_zero, &mpd_ctx) == 0) {
            break;
        }

        order_t *maker = (order_t*)node->value;
        if (mpd_cmp(taker->price, maker->price, &mpd_ctx) < 0) {
            break;
        }

        mpd_copy(price, maker->price, &mpd_ctx);
        if (mpd_cmp(taker->left, maker->left, &mpd_ctx) < 0) {
            mpd_copy(amount, taker->left, &mpd_ctx);
        } else {
            mpd_copy(amount, maker->left, &mpd_ctx);
        }

        mpd_mul(deal, price, amount, &mpd_ctx);
        mpd_mul(ask_fee, deal, maker->maker_fee, &mpd_ctx);
        mpd_mul(bid_fee, amount, taker->taker_fee, &mpd_ctx);

        taker->update_time = maker->update_time = current_timestamp();
        uint64_t deal_id = ++deals_id_start;
        if (real) {
            append_order_deal_history(taker->update_time, deal_id, maker, MARKET_ROLE_MAKER, taker, MARKET_ROLE_TAKER, price, amount, deal, ask_fee, bid_fee);
            push_deal_message(taker->update_time, this->name, maker, taker, price, amount, ask_fee, bid_fee, MARKET_ORDER_SIDE_BID, deal_id, this->stock, this->money);
        }

        mpd_sub(taker->left, taker->left, amount, &mpd_ctx);
        mpd_add(taker->deal_stock, taker->deal_stock, amount, &mpd_ctx);
        mpd_add(taker->deal_money, taker->deal_money, deal, &mpd_ctx);
        mpd_add(taker->deal_fee, taker->deal_fee, bid_fee, &mpd_ctx);

        balance_sub(taker->user_id, BALANCE_TYPE_AVAILABLE, this->money, deal);
        if (real) {
            append_balance_trade_sub(taker, this->money, deal, price, amount);
        }
        balance_add(taker->user_id, BALANCE_TYPE_AVAILABLE, this->stock, amount);
        if (real) {
            append_balance_trade_add(taker, this->stock, amount, price, amount);
        }
        if (mpd_cmp(bid_fee, mpd_zero, &mpd_ctx) > 0) {
            balance_sub(taker->user_id, BALANCE_TYPE_AVAILABLE, this->stock, bid_fee);
            if (real) {
                append_balance_trade_fee(taker, this->stock, bid_fee, price, amount, taker->taker_fee);
            }
        }

        mpd_sub(maker->left, maker->left, amount, &mpd_ctx);
        mpd_sub(maker->freeze, maker->freeze, amount, &mpd_ctx);
        mpd_add(maker->deal_stock, maker->deal_stock, amount, &mpd_ctx);
        mpd_add(maker->deal_money, maker->deal_money, deal, &mpd_ctx);
        mpd_add(maker->deal_fee, maker->deal_fee, ask_fee, &mpd_ctx);

        balance_sub(maker->user_id, BALANCE_TYPE_FREEZE, this->stock, amount);
        if (real) {
            append_balance_trade_sub(maker, this->stock, amount, price, amount);
        }
        balance_add(maker->user_id, BALANCE_TYPE_AVAILABLE, this->money, deal);
        if (real) {
            append_balance_trade_add(maker, this->money, deal, price, amount);
        }
        if (mpd_cmp(ask_fee, mpd_zero, &mpd_ctx) > 0) {
            balance_sub(maker->user_id, BALANCE_TYPE_AVAILABLE, this->money, ask_fee);
            if (real) {
                append_balance_trade_fee(maker, this->money, ask_fee, price, amount, maker->maker_fee);
            }
        }

        if (mpd_cmp(maker->left, mpd_zero, &mpd_ctx) == 0) {
            if (real) {
                push_order_message(ORDER_EVENT_FINISH, maker, this);
            }
            this->order_finish(real, maker);
        } else {
            if (real) {
                push_order_message(ORDER_EVENT_UPDATE, maker, this);
            }
        }
    }
    skiplist_release_iterator(iter);

    mpd_del(amount);
    mpd_del(price);
    mpd_del(deal);
    mpd_del(ask_fee);
    mpd_del(bid_fee);

    return 0;
}

int market_t::put_limit_order(bool real, json_t **result, uint32_t user_id, uint32_t side, mpd_t *amount, mpd_t *price, mpd_t *taker_fee, mpd_t *maker_fee, const char *source)
{
    if (side == MARKET_ORDER_SIDE_ASK) {
        mpd_t *balance = balance_get(user_id, BALANCE_TYPE_AVAILABLE, this->stock);
        if (!balance || mpd_cmp(balance, amount, &mpd_ctx) < 0) {
            return -1;
        }
    } else {
        mpd_t *balance = balance_get(user_id, BALANCE_TYPE_AVAILABLE, this->money);
        mpd_t *require = mpd_new(&mpd_ctx);
        mpd_mul(require, amount, price, &mpd_ctx);
        if (!balance || mpd_cmp(balance, require, &mpd_ctx) < 0) {
            mpd_del(require);
            return -1;
        }
        mpd_del(require);
    }

    if (mpd_cmp(amount, this->min_amount, &mpd_ctx) < 0) {
        return -2;
    }

    order_t *order = order_t::create(
            ++order_id_start, MARKET_ORDER_TYPE_LIMIT, side,
            this->name, source, user_id, amount, price, taker_fee, maker_fee);

    if (order == NULL) {
        return -__LINE__;
    }

    int ret;
    if (side == MARKET_ORDER_SIDE_ASK) {
        ret = this->execute_limit_ask_order(real, order);
    } else {
        ret = this->execute_limit_bid_order(real, order);
    }
    if (ret < 0) {
        log_error("execute order: %" PRIu64 " fail: %d", order->id, ret);
        order->release();
        return -__LINE__;
    }

    if (mpd_cmp(order->left, mpd_zero, &mpd_ctx) == 0) {
        if (real) {
            ret = append_order_history(order);
            if (ret < 0) {
                log_fatal("append_order_history fail: %d, order: %" PRIu64 "", ret, order->id);
            }
            push_order_message(ORDER_EVENT_FINISH, order, this);
            *result = get_order_info(order);
        }
        order->release();
    } else {
        if (real) {
            push_order_message(ORDER_EVENT_PUT, order, this);
            *result = get_order_info(order);
        }
        ret = this->order_put(order);
        if (ret < 0) {
            log_fatal("order_put fail: %d, order: %" PRIu64 "", ret, order->id);
        }
    }

    return 0;
}

int market_t::execute_market_ask_order(bool real, order_t *taker)
{
    mpd_t *price    = mpd_new(&mpd_ctx);
    mpd_t *amount   = mpd_new(&mpd_ctx);
    mpd_t *deal     = mpd_new(&mpd_ctx);
    mpd_t *ask_fee  = mpd_new(&mpd_ctx);
    mpd_t *bid_fee  = mpd_new(&mpd_ctx);

    skiplist_node *node;
    skiplist_iter *iter = skiplist_get_iterator(this->bids);
    while ((node = skiplist_next(iter)) != NULL) {
        if (mpd_cmp(taker->left, mpd_zero, &mpd_ctx) == 0) {
            break;
        }

        order_t *maker = (order_t*)node->value;
        mpd_copy(price, maker->price, &mpd_ctx);
        if (mpd_cmp(taker->left, maker->left, &mpd_ctx) < 0) {
            mpd_copy(amount, taker->left, &mpd_ctx);
        } else {
            mpd_copy(amount, maker->left, &mpd_ctx);
        }

        mpd_mul(deal, price, amount, &mpd_ctx);
        mpd_mul(ask_fee, deal, taker->taker_fee, &mpd_ctx);
        mpd_mul(bid_fee, amount, maker->maker_fee, &mpd_ctx);

        taker->update_time = maker->update_time = current_timestamp();
        uint64_t deal_id = ++deals_id_start;
        if (real) {
            append_order_deal_history(taker->update_time, deal_id, taker, MARKET_ROLE_TAKER, maker, MARKET_ROLE_MAKER, price, amount, deal, ask_fee, bid_fee);
            push_deal_message(taker->update_time, this->name, taker, maker, price, amount, ask_fee, bid_fee, MARKET_ORDER_SIDE_ASK, deal_id, this->stock, this->money);
        }

        mpd_sub(taker->left, taker->left, amount, &mpd_ctx);
        mpd_add(taker->deal_stock, taker->deal_stock, amount, &mpd_ctx);
        mpd_add(taker->deal_money, taker->deal_money, deal, &mpd_ctx);
        mpd_add(taker->deal_fee, taker->deal_fee, ask_fee, &mpd_ctx);

        balance_sub(taker->user_id, BALANCE_TYPE_AVAILABLE, this->stock, amount);
        if (real) {
            append_balance_trade_sub(taker, this->stock, amount, price, amount);
        }
        balance_add(taker->user_id, BALANCE_TYPE_AVAILABLE, this->money, deal);
        if (real) {
            append_balance_trade_add(taker, this->money, deal, price, amount);
        }
        if (mpd_cmp(ask_fee, mpd_zero, &mpd_ctx) > 0) {
            balance_sub(taker->user_id, BALANCE_TYPE_AVAILABLE, this->money, ask_fee);
            if (real) {
                append_balance_trade_fee(taker, this->money, ask_fee, price, amount, taker->taker_fee);
            }
        }

        mpd_sub(maker->left, maker->left, amount, &mpd_ctx);
        mpd_sub(maker->freeze, maker->freeze, deal, &mpd_ctx);
        mpd_add(maker->deal_stock, maker->deal_stock, amount, &mpd_ctx);
        mpd_add(maker->deal_money, maker->deal_money, deal, &mpd_ctx);
        mpd_add(maker->deal_fee, maker->deal_fee, bid_fee, &mpd_ctx);

        balance_sub(maker->user_id, BALANCE_TYPE_FREEZE, this->money, deal);
        if (real) {
            append_balance_trade_sub(maker, this->money, deal, price, amount);
        }
        balance_add(maker->user_id, BALANCE_TYPE_AVAILABLE, this->stock, amount);
        if (real) {
            append_balance_trade_add(maker, this->stock, amount, price, amount);
        }
        if (mpd_cmp(bid_fee, mpd_zero, &mpd_ctx) > 0) {
            balance_sub(maker->user_id, BALANCE_TYPE_AVAILABLE, this->stock, bid_fee);
            if (real) {
                append_balance_trade_fee(maker, this->stock, bid_fee, price, amount, maker->maker_fee);
            }
        }

        if (mpd_cmp(maker->left, mpd_zero, &mpd_ctx) == 0) {
            if (real) {
                push_order_message(ORDER_EVENT_FINISH, maker, this);
            }
            this->order_finish(real, maker);
        } else {
            if (real) {
                push_order_message(ORDER_EVENT_UPDATE, maker, this);
            }
        }
    }
    skiplist_release_iterator(iter);

    mpd_del(amount);
    mpd_del(price);
    mpd_del(deal);
    mpd_del(ask_fee);
    mpd_del(bid_fee);

    return 0;
}

int market_t::execute_market_bid_order(bool real, order_t *taker)
{
    mpd_t *price    = mpd_new(&mpd_ctx);
    mpd_t *amount   = mpd_new(&mpd_ctx);
    mpd_t *deal     = mpd_new(&mpd_ctx);
    mpd_t *ask_fee  = mpd_new(&mpd_ctx);
    mpd_t *bid_fee  = mpd_new(&mpd_ctx);
    mpd_t *result   = mpd_new(&mpd_ctx);

    skiplist_node *node;
    skiplist_iter *iter = skiplist_get_iterator(this->asks);
    while ((node = skiplist_next(iter)) != NULL) {
        if (mpd_cmp(taker->left, mpd_zero, &mpd_ctx) == 0) {
            break;
        }

        order_t *maker = (order_t*)node->value;
        mpd_copy(price, maker->price, &mpd_ctx);

        mpd_div(amount, taker->left, price, &mpd_ctx);
        mpd_rescale(amount, amount, -this->stock_prec, &mpd_ctx);
        while (true) {
            mpd_mul(result, amount, price, &mpd_ctx);
            if (mpd_cmp(result, taker->left, &mpd_ctx) > 0) {
                mpd_set_i32(result, -this->stock_prec, &mpd_ctx);
                mpd_pow(result, mpd_ten, result, &mpd_ctx);
                mpd_sub(amount, amount, result, &mpd_ctx);
            } else {
                break;
            }
        }

        if (mpd_cmp(amount, maker->left, &mpd_ctx) > 0) {
            mpd_copy(amount, maker->left, &mpd_ctx);
        }
        if (mpd_cmp(amount, mpd_zero, &mpd_ctx) == 0) {
            break;
        }

        mpd_mul(deal, price, amount, &mpd_ctx);
        mpd_mul(ask_fee, deal, maker->maker_fee, &mpd_ctx);
        mpd_mul(bid_fee, amount, taker->taker_fee, &mpd_ctx);

        taker->update_time = maker->update_time = current_timestamp();
        uint64_t deal_id = ++deals_id_start;
        if (real) {
            append_order_deal_history(taker->update_time, deal_id, maker, MARKET_ROLE_MAKER, taker, MARKET_ROLE_TAKER, price, amount, deal, ask_fee, bid_fee);
            push_deal_message(taker->update_time, this->name, maker, taker, price, amount, ask_fee, bid_fee, MARKET_ORDER_SIDE_BID, deal_id, this->stock, this->money);
        }

        mpd_sub(taker->left, taker->left, deal, &mpd_ctx);
        mpd_add(taker->deal_stock, taker->deal_stock, amount, &mpd_ctx);
        mpd_add(taker->deal_money, taker->deal_money, deal, &mpd_ctx);
        mpd_add(taker->deal_fee, taker->deal_fee, bid_fee, &mpd_ctx);

        balance_sub(taker->user_id, BALANCE_TYPE_AVAILABLE, this->money, deal);
        if (real) {
            append_balance_trade_sub(taker, this->money, deal, price, amount);
        }
        balance_add(taker->user_id, BALANCE_TYPE_AVAILABLE, this->stock, amount);
        if (real) {
            append_balance_trade_add(taker, this->stock, amount, price, amount);
        }
        if (mpd_cmp(bid_fee, mpd_zero, &mpd_ctx) > 0) {
            balance_sub(taker->user_id, BALANCE_TYPE_AVAILABLE, this->stock, bid_fee);
            if (real) {
                append_balance_trade_fee(taker, this->stock, bid_fee, price, amount, taker->taker_fee);
            }
        }

        mpd_sub(maker->left, maker->left, amount, &mpd_ctx);
        mpd_sub(maker->freeze, maker->freeze, amount, &mpd_ctx);
        mpd_add(maker->deal_stock, maker->deal_stock, amount, &mpd_ctx);
        mpd_add(maker->deal_money, maker->deal_money, deal, &mpd_ctx);
        mpd_add(maker->deal_fee, maker->deal_fee, ask_fee, &mpd_ctx);

        balance_sub(maker->user_id, BALANCE_TYPE_FREEZE, this->stock, amount);
        if (real) {
            append_balance_trade_sub(maker, this->stock, amount, price, amount);
        }
        balance_add(maker->user_id, BALANCE_TYPE_AVAILABLE, this->money, deal);
        if (real) {
            append_balance_trade_add(maker, this->money, deal, price, amount);
        }
        if (mpd_cmp(ask_fee, mpd_zero, &mpd_ctx) > 0) {
            balance_sub(maker->user_id, BALANCE_TYPE_AVAILABLE, this->money, ask_fee);
            if (real) {
                append_balance_trade_fee(maker, this->money, ask_fee, price, amount, maker->maker_fee);
            }
        }

        if (mpd_cmp(maker->left, mpd_zero, &mpd_ctx) == 0) {
            if (real) {
                push_order_message(ORDER_EVENT_FINISH, maker, this);
            }
            this->order_finish(real, maker);
        } else {
            if (real) {
                push_order_message(ORDER_EVENT_UPDATE, maker, this);
            }
        }
    }
    skiplist_release_iterator(iter);

    mpd_del(amount);
    mpd_del(price);
    mpd_del(deal);
    mpd_del(ask_fee);
    mpd_del(bid_fee);
    mpd_del(result);

    return 0;
}

int market_t::put_market_order(bool real, json_t **result, uint32_t user_id, uint32_t side, mpd_t *amount, mpd_t *taker_fee, const char *source)
{
    if (side == MARKET_ORDER_SIDE_ASK) {
        mpd_t *balance = balance_get(user_id, BALANCE_TYPE_AVAILABLE, this->stock);
        if (!balance || mpd_cmp(balance, amount, &mpd_ctx) < 0) {
            return -1;
        }

        skiplist_iter *iter = skiplist_get_iterator(this->bids);
        skiplist_node *node = skiplist_next(iter);
        if (node == NULL) {
            skiplist_release_iterator(iter);
            return -3;
        }
        skiplist_release_iterator(iter);

        if (mpd_cmp(amount, this->min_amount, &mpd_ctx) < 0) {
            return -2;
        }
    } else {
        mpd_t *balance = balance_get(user_id, BALANCE_TYPE_AVAILABLE, this->money);
        if (!balance || mpd_cmp(balance, amount, &mpd_ctx) < 0) {
            return -1;
        }

        skiplist_iter *iter = skiplist_get_iterator(this->asks);
        skiplist_node *node = skiplist_next(iter);
        if (node == NULL) {
            skiplist_release_iterator(iter);
            return -3;
        }
        skiplist_release_iterator(iter);

        order_t *order = (order_t*)node->value;
        mpd_t *require = mpd_new(&mpd_ctx);
        mpd_mul(require, order->price, this->min_amount, &mpd_ctx);
        if (mpd_cmp(amount, require, &mpd_ctx) < 0) {
            mpd_del(require);
            return -2;
        }
        mpd_del(require);
    }

    order_t *order = order_t::create(
            ++order_id_start, MARKET_ORDER_TYPE_MARKET, side,
            this->name, source, user_id, amount, mpd_zero, taker_fee, mpd_zero);

    if (order == NULL) {
        return -__LINE__;
    }

    int ret;
    if (side == MARKET_ORDER_SIDE_ASK) {
        ret = this->execute_market_ask_order(real, order);
    } else {
        ret = this->execute_market_bid_order(real, order);
    }
    if (ret < 0) {
        log_error("execute order: %" PRIu64 " fail: %d", order->id, ret);
        order->release();
        return -__LINE__;
    }

    if (real) {
        int ret = append_order_history(order);
        if (ret < 0) {
            log_fatal("append_order_history fail: %d, order: %" PRIu64 "", ret, order->id);
        }
        push_order_message(ORDER_EVENT_FINISH, order, this);
        *result = get_order_info(order);
    }

    order->release();
    return 0;
}

int market_t::cancel_order(bool real, json_t **result, order_t *order)
{
    if (real) {
        push_order_message(ORDER_EVENT_FINISH, order, this);
        *result = get_order_info(order);
    }
    this->order_finish(real, order);
    return 0;
}

int market_t::put_order(order_t *order)
{
    return this->order_put(order);
}

order_t *market_t::get_order(uint64_t order_id)
{
    dict_id_key key(order_id);
    auto entry = this->orders->find(&key);
    if (entry) {
        return entry->get_value();
    }
    return NULL;
}

skiplist_t *market_t::get_order_list(uint32_t user_id)
{
    dict_id_key key(user_id);
    auto entry = this->users->find(&key);
    if (entry) {
        return entry->get_value();
    }
    return NULL;
}

int market_t::get_status(size_t *ask_count, mpd_t *ask_amount, size_t *bid_count, mpd_t *bid_amount)
{
    *ask_count = this->asks->len;
    *bid_count = this->bids->len;
    mpd_copy(ask_amount, mpd_zero, &mpd_ctx);
    mpd_copy(bid_amount, mpd_zero, &mpd_ctx);

    skiplist_node *node;
    skiplist_iter *iter = skiplist_get_iterator(this->asks);
    while ((node = skiplist_next(iter)) != NULL) {
        order_t *order = (order_t*)node->value;
        mpd_add(ask_amount, ask_amount, order->left, &mpd_ctx);
    }
    skiplist_release_iterator(iter);

    iter = skiplist_get_iterator(this->bids);
    while ((node = skiplist_next(iter)) != NULL) {
        order_t *order = (order_t*)node->value;
        mpd_add(bid_amount, bid_amount, order->left, &mpd_ctx);
    }

    return 0;
}

sds market_t::status(sds reply)
{
    reply = sdscatprintf(reply, "order last ID: %" PRIu64 "\n", order_id_start);
    reply = sdscatprintf(reply, "deals last ID: %" PRIu64 "\n", deals_id_start);
    return reply;
}

